Interrupt-driven serial transmit for 8250/16550 UARTs.
Signed-off-by: Keir Fraser <keir@xensource.com>
local_irq_save(flags);
watchdog_disable();
+ console_start_sync();
/* Shouldn't really do this, but otherwise we stop for no
obvious reason, which is Bad */
ASSERT(!local_irq_is_enabled());
}
}
+
+ console_end_sync();
watchdog_enable();
atomic_inc(&xendbg_running);
+
local_irq_restore(flags);
+
return 0;
}
safe_halt();
}
+ watchdog_disable();
+ console_start_sync();
+
local_irq_enable();
/* Ensure we are the boot CPU. */
void machine_halt(void)
{
watchdog_disable();
+ console_start_sync();
smp_call_function(__machine_halt, NULL, 1, 0);
__machine_halt(NULL);
}
};
watchdog_disable();
+ console_start_sync();
show_registers(regs);
spin_lock(&console_lock);
}
+void console_start_sync(void)
+{
+ serial_start_sync(sercon_handle);
+}
+
+void console_end_sync(void)
+{
+ serial_end_sync(sercon_handle);
+}
+
void console_putc(char c)
{
serial_putc(sercon_handle, c);
static void ns16550_interrupt(
int irq, void *dev_id, struct cpu_user_regs *regs)
{
- serial_rx_interrupt(dev_id, regs);
+ struct serial_port *port = dev_id;
+ struct ns16550 *uart = port->uart;
+
+ if ( (ns_read_reg(uart, IIR) & 7) == 2 )
+ serial_tx_interrupt(port, regs);
+ else
+ serial_rx_interrupt(port, regs);
}
-static void ns16550_putc(struct serial_port *port, char c)
+static int ns16550_tx_empty(struct serial_port *port)
{
struct ns16550 *uart = port->uart;
+ return !!(ns_read_reg(uart, LSR) & LSR_THRE);
+}
- while ( !(ns_read_reg(uart, LSR) & LSR_THRE) )
- cpu_relax();
-
+static void ns16550_putc(struct serial_port *port, char c)
+{
+ struct ns16550 *uart = port->uart;
ns_write_reg(uart, THR, c);
}
/* Enable and clear the FIFOs. Set a large trigger threshold. */
ns_write_reg(uart, FCR, FCR_ENABLE | FCR_CLRX | FCR_CLTX | FCR_TRG14);
+
+ /* Check this really is a 16550+. Otherwise we have no FIFOs. */
+ if ( (ns_read_reg(uart, IIR) & 0xc0) == 0xc0 )
+ port->tx_fifo_size = 16;
}
static void ns16550_init_postirq(struct serial_port *port)
struct ns16550 *uart = port->uart;
int rc;
+ serial_async_transmit(port);
+
uart->irqaction.handler = ns16550_interrupt;
uart->irqaction.name = "ns16550";
uart->irqaction.dev_id = port;
if ( (rc = setup_irq(uart->irq, &uart->irqaction)) != 0 )
printk("ERROR: Failed to allocate na16550 IRQ %d\n", uart->irq);
- /* For sanity, clear the receive FIFO. */
- ns_write_reg(uart, FCR, FCR_ENABLE | FCR_CLRX | FCR_TRG14);
-
/* Master interrupt enable; also keep DTR/RTS asserted. */
ns_write_reg(uart, MCR, MCR_OUT2 | MCR_DTR | MCR_RTS);
- /* Enable receive interrupts. */
- ns_write_reg(uart, IER, IER_ERDAI);
+ /* Enable receive and transmit interrupts. */
+ ns_write_reg(uart, IER, IER_ERDAI | IER_ETHREI);
}
#ifdef CONFIG_X86
.init_preirq = ns16550_init_preirq,
.init_postirq = ns16550_init_postirq,
.endboot = ns16550_endboot,
+ .tx_empty = ns16550_tx_empty,
.putc = ns16550_putc,
.getc = ns16550_getc
};
fn = port->rx_hi;
else if ( !(c & 0x80) && (port->rx_lo != NULL) )
fn = port->rx_lo;
- else if ( (port->rxbufp - port->rxbufc) != RXBUFSZ )
- port->rxbuf[MASK_RXBUF_IDX(port->rxbufp++)] = c;
+ else if ( (port->rxbufp - port->rxbufc) != SERIAL_RXBUFSZ )
+ port->rxbuf[MASK_SERIAL_RXBUF_IDX(port->rxbufp++)] = c;
spin_unlock_irqrestore(&port->lock, flags);
spin_unlock_irqrestore(&port->lock, flags);
}
+void serial_tx_interrupt(struct serial_port *port, struct cpu_user_regs *regs)
+{
+ int i;
+ unsigned long flags;
+
+ BUG_ON(!port->driver);
+ BUG_ON(!port->driver->tx_empty);
+ BUG_ON(!port->driver->putc);
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ for ( i = 0; i < port->tx_fifo_size; i++ )
+ {
+ if ( port->txbufc == port->txbufp )
+ break;
+ port->driver->putc(
+ port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]);
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+static void __serial_putc(struct serial_port *port, char c)
+{
+ int i;
+
+ if ( (port->txbuf != NULL) && !port->sync )
+ {
+ /* Interrupt-driven (asynchronous) transmitter. */
+ if ( (port->txbufp - port->txbufc) == SERIAL_TXBUFSZ )
+ {
+ /* Buffer is full: we spin, but could alternatively drop chars. */
+ while ( !port->driver->tx_empty(port) )
+ cpu_relax();
+ for ( i = 0; i < port->tx_fifo_size; i++ )
+ port->driver->putc(
+ port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]);
+ port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufp++)] = c;
+ }
+ else if ( ((port->txbufp - port->txbufc) == 0) &&
+ port->driver->tx_empty(port) )
+ {
+ /* Buffer and UART FIFO are both empty. */
+ port->driver->putc(port, c);
+ }
+ else
+ {
+ /* Normal case: buffer the character. */
+ port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufp++)] = c;
+ }
+ }
+ else if ( port->driver->tx_empty )
+ {
+ /* Synchronous finite-capacity transmitter. */
+ while ( !port->driver->tx_empty(port) )
+ cpu_relax();
+ port->driver->putc(port, c);
+ }
+ else
+ {
+ /* Simple synchronous transmitter. */
+ port->driver->putc(port, c);
+ }
+}
+
void serial_putc(int handle, char c)
{
struct serial_port *port = &com[handle & SERHND_IDX];
spin_lock_irqsave(&port->lock, flags);
if ( (c == '\n') && (handle & SERHND_COOKED) )
- port->driver->putc(port, '\r');
+ __serial_putc(port, '\r');
if ( handle & SERHND_HI )
c |= 0x80;
else if ( handle & SERHND_LO )
c &= 0x7f;
- port->driver->putc(port, c);
+ __serial_putc(port, c);
spin_unlock_irqrestore(&port->lock, flags);
}
if ( port->rxbufp != port->rxbufc )
{
- c = port->rxbuf[MASK_RXBUF_IDX(port->rxbufc++)];
+ c = port->rxbuf[MASK_SERIAL_RXBUF_IDX(port->rxbufc++)];
break;
}
struct serial_port *port = &com[handle & SERHND_IDX];
if ( handle != -1 )
port->lock = SPIN_LOCK_UNLOCKED;
+ serial_start_sync(handle);
+}
+
+void serial_start_sync(int handle)
+{
+ struct serial_port *port = &com[handle & SERHND_IDX];
+ unsigned long flags;
+
+ if ( handle == -1 )
+ return;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ if ( port->sync++ == 0 )
+ {
+ while ( (port->txbufp - port->txbufc) != 0 )
+ {
+ while ( !port->driver->tx_empty(port) )
+ cpu_relax();
+ port->driver->putc(
+ port, port->txbuf[MASK_SERIAL_TXBUF_IDX(port->txbufc++)]);
+ }
+ }
+
+ spin_unlock_irqrestore(&port->lock, flags);
+}
+
+void serial_end_sync(int handle)
+{
+ struct serial_port *port = &com[handle & SERHND_IDX];
+ unsigned long flags;
+
+ if ( handle == -1 )
+ return;
+
+ spin_lock_irqsave(&port->lock, flags);
+
+ port->sync--;
+
+ spin_unlock_irqrestore(&port->lock, flags);
}
void serial_init_preirq(void)
void serial_register_uart(int idx, struct uart_driver *driver, void *uart)
{
+ /* Store UART-specific info. */
com[idx].driver = driver;
com[idx].uart = uart;
+
+ /* Default is no transmit FIFO. */
+ com[idx].tx_fifo_size = 1;
+}
+
+void serial_async_transmit(struct serial_port *port)
+{
+ BUG_ON(!port->driver->tx_empty);
+ if ( !port->txbuf )
+ port->txbuf = (char *)alloc_xenheap_pages(get_order(SERIAL_TXBUFSZ));
}
/*
void console_force_unlock(void);
void console_force_lock(void);
+void console_start_sync(void);
+void console_end_sync(void);
+
#endif /* __CONSOLE_H__ */
void serial_set_rx_handler(int handle, serial_rx_fn fn);
/* Number of characters we buffer for a polling receiver. */
-#define RXBUFSZ 32
-#define MASK_RXBUF_IDX(_i) ((_i)&(RXBUFSZ-1))
+#define SERIAL_RXBUFSZ 32
+#define MASK_SERIAL_RXBUF_IDX(_i) ((_i)&(SERIAL_RXBUFSZ-1))
+
+/* Number of characters we buffer for an interrupt-driven transmitter. */
+#define SERIAL_TXBUFSZ 16384
+#define MASK_SERIAL_TXBUF_IDX(_i) ((_i)&(SERIAL_TXBUFSZ-1))
struct uart_driver;
/* Uart-driver parameters. */
struct uart_driver *driver;
void *uart;
+ /* Number of characters the port can hold for transmit. */
+ int tx_fifo_size;
+ /* Transmit data buffer (interrupt-driven uart). */
+ char *txbuf;
+ unsigned int txbufp, txbufc;
+ /* Force synchronous transmit. */
+ int sync;
/* Receiver callback functions (asynchronous receivers). */
serial_rx_fn rx_lo, rx_hi, rx;
/* Receive data buffer (polling receivers). */
- char rxbuf[RXBUFSZ];
+ char rxbuf[SERIAL_RXBUFSZ];
unsigned int rxbufp, rxbufc;
/* Serial I/O is concurrency-safe. */
spinlock_t lock;
void (*init_postirq)(struct serial_port *);
/* Hook to clean up after Xen bootstrap (before domain 0 runs). */
void (*endboot)(struct serial_port *);
- /* Put a char onto the serial line. */
+ /* Transmit FIFO ready to receive up to @tx_fifo_size characters? */
+ int (*tx_empty)(struct serial_port *);
+ /* Put a character onto the serial line. */
void (*putc)(struct serial_port *, char);
- /* Get a char from the serial line: returns FALSE if no char available. */
+ /* Get a character from the serial line: returns 0 if none available. */
int (*getc)(struct serial_port *, char *);
};
char serial_getc(int handle);
/* Forcibly prevent serial lockup when the system is in a bad way. */
+/* (NB. This also forces an implicit serial_start_sync()). */
void serial_force_unlock(int handle);
+/* Start/end a synchronous region (temporarily disable interrupt-driven tx). */
+void serial_start_sync(int handle);
+void serial_end_sync(int handle);
+
+/*
+ * Initialisation and helper functions for uart drivers.
+ */
/* Register a uart on serial port @idx (e.g., @idx==0 is COM1). */
void serial_register_uart(int idx, struct uart_driver *driver, void *uart);
-
-/* Driver helper function: process receive work in interrupt context. */
+/* Place the serial port into asynchronous transmit mode. */
+void serial_async_transmit(struct serial_port *port);
+/* Process work in interrupt context. */
void serial_rx_interrupt(struct serial_port *port, struct cpu_user_regs *regs);
+void serial_tx_interrupt(struct serial_port *port, struct cpu_user_regs *regs);
/*
* Initialisers for individual uart drivers.